package com.wg.core.thread.scheduler;

import com.wg.core.lang.IDisposable;
import com.wg.core.thread.IExecutionContext;
import com.wg.core.thread.IThreadPool;

import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class Scheduler implements ISchedulerRegistry, IScheduler, IDisposable {
    private final IExecutionContext executionContext;
    private final List<IDisposable> pending = new CopyOnWriteArrayList<>();
    private volatile boolean isRun = true;

    public Scheduler(IExecutionContext executionContext) {
        this.executionContext = executionContext;
    }

    @Override
    public IDisposable schedule(Runnable task, int firstInMs) {
        if (!this.isRun) {
            return () -> {
            };
        }

        if (firstInMs <= 0L) {
            PendingAction action = new PendingAction(task);
            this.executionContext.enqueue(action::execute);
            return action;
        }
        TimerAction pending = new TimerAction(this, task, firstInMs, -1L);
        this.addPending(pending);
        return pending;
    }

    @Override
    public IDisposable scheduleOnInterval(Runnable task, int firstInMs, int regularInMs) {
        if (!this.isRun) {
            return () -> {
            };
        }

        TimerAction pending = new TimerAction(this, task, firstInMs, regularInMs);
        this.addPending(pending);
        return pending;
    }

    @Override
    public void dispose() {
        if (!this.isRun) {
            return;
        }

        this.isRun = false;
        for (IDisposable iDisposable : pending) {
            iDisposable.dispose();
        }
        pending.clear();
    }

    @Override
    public void enqueue(Runnable task) {
        this.executionContext.enqueue(task);
    }

    @Override
    public void remove(IDisposable toRemove) {
        this.executionContext.enqueue(() -> this.pending.remove(toRemove));
    }

    @Override
    public IThreadPool getThreadPool() {
        return this.executionContext.getThreadPool();
    }

    private void addPending(TimerAction pending) {
        this.executionContext.enqueue(() -> pendingSchedule(pending));
    }

    private void pendingSchedule(TimerAction pending) {
        if (this.isRun) {
            this.pending.add(pending);
            pending.schedule();
        }
    }
}
